//! Some utils functions
use crate::{SERVER_INFO, webrtc::RoomId};
use base::consts::SessionID;
use rand::Rng;
use snowdon::{
    ClassicLayout, ClassicLayoutSnowflakeExtension, Epoch, Generator, MachineId, Snowflake,
};
use std::sync::LazyLock;
use tokio::task::JoinHandle;

pub struct SnowflakeParams;

impl Epoch for SnowflakeParams {
    fn millis_since_unix() -> u64 {
        1288834974657
    }
}

impl MachineId for SnowflakeParams {
    fn machine_id() -> u64 {
        SERVER_INFO.machine_id
    }
}

pub type MySnowflake = Snowflake<ClassicLayout<SnowflakeParams>, SnowflakeParams>;
pub type MySnowflakeGenerator = Generator<ClassicLayout<SnowflakeParams>, SnowflakeParams>;

/// A Generator of Snowflake, only being created to generate user id
pub static USER_ID_GENERATOR: LazyLock<MySnowflakeGenerator> =
    LazyLock::new(MySnowflakeGenerator::default);
pub static WEBRTC_ROOM_ID_GENERATOR: LazyLock<MySnowflakeGenerator> =
    LazyLock::new(MySnowflakeGenerator::default);
pub static SESSION_ID_GENERATOR: LazyLock<MySnowflakeGenerator> =
    LazyLock::new(MySnowflakeGenerator::default);

/// Generate ocid by random
pub fn generate_ocid(bits: usize) -> String {
    generate_random_string(bits)
}

/// Generate a random string of given length
///
/// The string is generated by a cryptographically secure PRNG and consists of
/// alphanumeric characters (a-z, A-Z, 0-9). The string is suitable for use as
/// a password or a secret key.
///
/// # Arguments
///
/// * `len`: The length of the string to generate.
///
/// # Examples
///
/// ```
/// use server::helper::generate_random_string;
/// let s = generate_random_string(10);
/// assert_eq!(s.len(), 10);
/// assert!(s.chars().all(char::is_alphanumeric));
/// ```
///
pub fn generate_random_string(len: usize) -> String {
    let rng = rand::rng();
    rng.sample_iter(rand::distr::Alphanumeric)
        .map(char::from)
        .take(len)
        .collect()
}

pub fn spawn_blocking_with_tracing<F, R>(f: F) -> JoinHandle<R>
where
    F: FnOnce() -> R + Send + 'static,
    R: Send + 'static,
{
    let current_span = tracing::Span::current();
    tokio::task::spawn_blocking(move || current_span.in_scope(f))
}

pub async fn create_file_with_dirs_if_not_exist<T: AsRef<std::path::Path>>(
    path: T,
) -> std::io::Result<tokio::fs::File> {
    tokio::fs::create_dir_all(std::path::Path::new(path.as_ref()).parent().unwrap()).await?;
    tokio::fs::File::create(path).await
}

pub fn generate_session_id() -> anyhow::Result<SessionID> {
    Ok(SESSION_ID_GENERATOR.generate()?.into_i64().into())
}

pub fn generate_webrtc_room_id() -> anyhow::Result<RoomId> {
    Ok(RoomId(
        WEBRTC_ROOM_ID_GENERATOR.generate()?.into_i64() as u64
    ))
}

/// Get an available port on the system.
///
/// This function creates a TCP listener bound to the "0.0.0.0:0" address,
/// which allows the operating system to assign an available port. It then
/// retrieves and returns the port number assigned by the OS.
///
/// # Returns
///
/// * `u16` - An available port number.
///
/// # Panics
///
/// This function will panic if it fails to bind to an address or retrieve the
/// local address of the listener.
pub fn get_available_port() -> u16 {
    std::net::TcpListener::bind("0.0.0.0:0")
        .unwrap()
        .local_addr()
        .unwrap()
        .port()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_generate_random_string() {
        let s = generate_random_string(10);
        assert_eq!(s.len(), 10);
    }

    #[test]
    fn test_generate_ocid() {
        let s = generate_ocid(10);
        assert_eq!(s.len(), 10);
    }
}
